package com.hsurosy.hsuchain.nft.base.utils;

import com.alibaba.fastjson2.JSON;
import com.google.common.collect.ImmutableSet;
import com.hsurosy.hsuchain.nft.base.exception.RemoteCallException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;

import static com.hsurosy.hsuchain.nft.base.exception.BizErrorCode.REMOTE_CALL_RESPONSE_IS_FAILED;
import static com.hsurosy.hsuchain.nft.base.exception.BizErrorCode.REMOTE_CALL_RESPONSE_IS_NULL;

/**
 * 远程方法调用包装工具类
 *
 * @Author Hsu琛君珩
 * @Date 2024-10-04 18:54
 * @Description
 * @Version: v1.0.0
 */
public class RemoteCallWrapper {

    private static final Logger logger = LoggerFactory.getLogger(RemoteCallWrapper.class);

    // 需要检查的成功方法名集合
    private static final ImmutableSet<String> SUCCESS_CHECK_METHOD = ImmutableSet.of("isSuccess", "isSucceeded", "getSuccess");

    // 需要检查的响应码方法名集合
    private static final ImmutableSet<String> SUCCESS_CODE_METHOD = ImmutableSet.of("getResponseCode");

    // 合法的响应码集合
    private static final ImmutableSet<String> SUCCESS_CODE = ImmutableSet.of("SUCCESS", "DUPLICATE", "DUPLICATED_REQUEST");

    /**
     * 执行远程调用
     *
     * @param function 调用的函数
     * @param request 请求对象
     * @param checkResponse 是否检查响应
     * @param <T> 请求类型
     * @param <R> 响应类型
     * @return 响应结果
     */
    public static <T, R> R call(Function<T, R> function, T request, boolean checkResponse) {
        return call(function, request, request.getClass().getSimpleName(), checkResponse, false);
    }

    /**
     * 执行远程调用，默认检查响应
     *
     * @param function 调用的函数
     * @param request 请求对象
     * @param <T> 请求类型
     * @param <R> 响应类型
     * @return 响应结果
     */
    public static <T, R> R call(Function<T, R> function, T request) {
        return call(function, request, request.getClass().getSimpleName(), true, false);
    }

    /**
     * 执行远程调用，带有请求名称
     *
     * @param function 调用的函数
     * @param request 请求对象
     * @param requestName 请求名称
     * @param <T> 请求类型
     * @param <R> 响应类型
     * @return 响应结果
     */
    public static <T, R> R call(Function<T, R> function, T request, String requestName) {
        return call(function, request, requestName, true, false);
    }

    /**
     * 执行远程调用，带有响应码检查
     *
     * @param function 调用的函数
     * @param request 请求对象
     * @param checkResponse 是否检查响应
     * @param checkResponseCode 是否检查响应码
     * @param <T> 请求类型
     * @param <R> 响应类型
     * @return 响应结果
     */
    public static <T, R> R call(Function<T, R> function, T request, boolean checkResponse, boolean checkResponseCode) {
        return call(function, request, request.getClass().getSimpleName(), checkResponse, checkResponseCode);
    }

    /**
     * 执行远程调用，处理日志记录与响应校验
     *
     * @param function 调用的函数
     * @param request 请求对象
     * @param requestName 请求名称
     * @param checkResponse 是否检查响应
     * @param checkResponseCode 是否检查响应码
     * @param <T> 请求类型
     * @param <R> 响应类型
     * @return 响应结果
     */
    public static <T, R> R call(Function<T, R> function, T request, String requestName, boolean checkResponse,
                                boolean checkResponseCode) {
        StopWatch stopWatch = new StopWatch();
        R response = null;
        try {
            stopWatch.start();
            response = function.apply(request);
            stopWatch.stop();

            if (checkResponse) {
                Assert.notNull(response, REMOTE_CALL_RESPONSE_IS_NULL.name());

                if (!isResponseValid(response)) {
                    logger.error("Response Invalid on Remote Call request {} , response {}",
                            JSON.toJSONString(request), JSON.toJSONString(response));

                    throw new RemoteCallException(JSON.toJSONString(response), REMOTE_CALL_RESPONSE_IS_FAILED);
                }
            }
            if (checkResponseCode) {
                Assert.notNull(response, REMOTE_CALL_RESPONSE_IS_NULL.name());

                if (!isResponseCodeValid(response)) {
                    logger.error("Response code Invalid on Remote Call request {} , response {}",
                            JSON.toJSONString(request), JSON.toJSONString(response));

                    throw new RemoteCallException(JSON.toJSONString(response), REMOTE_CALL_RESPONSE_IS_FAILED);
                }
            }

        } catch (IllegalAccessException | InvocationTargetException e) {
            logger.error("Catch Exception on Remote Call :" + e.getMessage(), e);
            throw new IllegalArgumentException("Catch Exception on Remote Call " + e.getMessage(), e);
        } catch (Throwable e) {
            logger.error("request exception {}", JSON.toJSONString(request));
            logger.error("Catch Exception on Remote Call :" + e.getMessage(), e);
            throw e;
        } finally {
            if (logger.isInfoEnabled()) {
                logger.info("## Method={} ,## 耗时={}ms ,## [请求报文]:{},## [响应报文]:{}", requestName,
                        stopWatch.getTotalTimeMillis(),
                        JSON.toJSONString(request), JSON.toJSONString(response));
            }
        }

        return response;
    }

    /**
     * 校验响应是否有效
     *
     * @param response 响应对象
     * @param <R> 响应类型
     * @return 响应是否有效
     * @throws IllegalAccessException 异常
     * @throws InvocationTargetException 异常
     */
    private static <R> boolean isResponseValid(R response) throws IllegalAccessException, InvocationTargetException {
        Method successMethod = null;
        Method[] methods = response.getClass().getMethods();
        for (Method method : methods) {
            if (SUCCESS_CHECK_METHOD.contains(method.getName())) {
                successMethod = method;
                break;
            }
        }
        return successMethod == null || (Boolean) successMethod.invoke(response);
    }

    /**
     * 校验响应码是否有效
     *
     * @param response 响应对象
     * @param <R> 响应类型
     * @return 响应码是否有效
     * @throws IllegalAccessException 异常
     * @throws InvocationTargetException 异常
     */
    private static <R> boolean isResponseCodeValid(R response) throws IllegalAccessException, InvocationTargetException {
        Method successMethod = null;
        Method[] methods = response.getClass().getMethods();
        for (Method method : methods) {
            if (SUCCESS_CODE_METHOD.contains(method.getName())) {
                successMethod = method;
                break;
            }
        }
        return successMethod == null || SUCCESS_CODE.contains(successMethod.invoke(response));
    }
}
